// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RoleEntryPoint.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   A Worker Role that uses the <see cref="IRoleEnvironment" /> service for testability
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Primitives;
using System.Linq;

using AzureContrib.WindowsAzure.Diagnostics;
using AzureContrib.WindowsAzure.Properties;
using AzureContrib.WindowsAzure.ServiceRuntime.ServiceLocation;
using AzureContrib.WindowsAzure.WorkServices;

namespace AzureContrib.WindowsAzure.ServiceRuntime
{
	/// <summary>
	/// A Worker Role that uses the <see cref="IRoleEnvironment"/> service for testability
	/// </summary>
	public abstract class RoleEntryPoint : Microsoft.WindowsAzure.ServiceRuntime.RoleEntryPoint
	{
		#region Constants and Fields

		[Import]
		private Lazy<ICrashDumps> lazyCrashDumps;

		[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
		private Lazy<IRoleEnvironment> lazyRoleEnvironment;

		[Import]
		private Lazy<ITraceFactory> lazyTraceFactory;

		[Import]
		private Lazy<IWorkProvider> lazyWorkProvider;

		private MefProvider mefProvider;

		private ITrace trace;

		#endregion

		#region Public Properties

		/// <summary>
		/// Gets or sets <see cref="ICrashDumps"/>.
		/// </summary>
		public ICrashDumps CrashDumps
		{
			get
			{
				return lazyCrashDumps != null
				       	? lazyCrashDumps.Value
				       	: GetLazyValueOrMEFImport(lazyCrashDumps) ?? lazyCrashDumps.Value;
			}

			set
			{
				lazyCrashDumps = new Lazy<ICrashDumps>(() => value);
			}
		}

		/// <summary>
		/// Gets or sets the event fired just before initiating the instance with MEF imports.
		/// </summary>
		public EventHandler<MEFImportEventArgs> MefImporting { get; set; }

		/// <summary>
		/// Gets or sets the <see cref="IRoleEnvironment"/>
		/// </summary>
		public IRoleEnvironment RoleEnvironment
		{
			get
			{
				return lazyRoleEnvironment != null
				       	? lazyRoleEnvironment.Value
				       	: GetLazyValueOrMEFImport(lazyRoleEnvironment) ?? lazyRoleEnvironment.Value;
			}

			set
			{
				lazyRoleEnvironment = new Lazy<IRoleEnvironment>(() => value);
			}
		}

		/// <summary>
		/// Gets or sets the <see cref="IWorkProvider"/>.
		/// </summary>
		public IWorkProvider WorkProvider
		{
			get
			{
				IWorkProvider workProvider = lazyWorkProvider != null
				                             	? lazyWorkProvider.Value
				                             	: GetLazyValueOrMEFImport(lazyWorkProvider) ?? lazyWorkProvider.Value;
				workProvider.SetBuilder(Builder);
				return workProvider;
			}

			set
			{
				lazyWorkProvider = new Lazy<IWorkProvider>(() => value);
			}
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the <see cref="ITrace"/>
		/// </summary>
		internal ITrace Trace
		{
			get
			{
				return trace ?? (trace = TraceFactory.Create(GetType()));
			}

			set
			{
				trace = value;
			}
		}

		internal ITraceFactory TraceFactory
		{
			get
			{
				return lazyTraceFactory != null
				       	? lazyTraceFactory.Value
				       	: GetLazyValueOrMEFImport(lazyTraceFactory) ?? lazyTraceFactory.Value;
			}

			set
			{
				lazyTraceFactory = new Lazy<ITraceFactory>(() => value);
			}
		}

		/// <summary>
		/// Gets or sets a <see cref="IBuilder"/> which can optionally be provided by the implementer.
		/// </summary>
		protected IBuilder Builder { private get; set; }

		#endregion

		#region Public Methods

		/// <summary>
		/// Running any and all <see cref="IWorkItem"/>s that are of type <see cref="WorkType.OnStart"/>
		/// </summary>
		/// <returns>
		/// The on start.
		/// </returns>
		public override bool OnStart()
		{
			IEnumerable<IWork> allOnStartWork = WorkProvider.GetAllWork(WorkType.OnStart);
			int onStartWorkCount = allOnStartWork.Count();
			if (onStartWorkCount > 0)
			{
				Trace.Information(Strings.RunningAllOnStartWork, onStartWorkCount);

				bool hasDoneSomeWork = false;
				foreach (IWork workItem in allOnStartWork)
				{
					if (workItem.HasWork)
					{
						workItem.DoWork();
						hasDoneSomeWork = true;
					}
				}

				if (!hasDoneSomeWork)
				{
					Trace.Information(Strings.NoOnStartWorkRequired);
				}

				Trace.Information(Strings.DoneWithOnStartWorkItems);
			}
			else
			{
				Trace.Information(Strings.NoOnStartWorkDeployed);
			}

			bool result = base.OnStart();
			Trace.Information(Strings.RoleInitiated);
			return result;
		}

		/// <summary>
		/// Running any and all <see cref="IWorkItem"/>s that are of type <see cref="WorkType.OnStop"/>
		/// </summary>
		public override void OnStop()
		{
			IEnumerable<IWork> allOnStopWork = WorkProvider.GetAllWork(WorkType.OnStop);
			int onStopWorkCount = allOnStopWork.Count();
			if (onStopWorkCount > 0)
			{
				Trace.Information(Strings.RunningAllOnStopWork, onStopWorkCount);

				bool hasDoneSomeWork = false;
				foreach (IWork workItem in allOnStopWork)
				{
					if (workItem.HasWork)
					{
						workItem.DoWork();
						hasDoneSomeWork = true;
					}
				}

				if (!hasDoneSomeWork)
				{
					Trace.Information(Strings.NoOnStopWorkRequired);
				}

				Trace.Information(Strings.DoneWithOnStopWorkItems);
			}
			else
			{
				Trace.Information(Strings.NoOnStopWorkDeployed);
			}

			base.OnStop();
			Trace.Information(Strings.RoleShuttingDown);
		}

		#endregion

		#region Methods

		private T GetLazyValueOrMEFImport<T>(Lazy<T> lazy) where T : class
		{
			if (lazy == null)
			{
				MefImport();
				return null;
			}

			return lazy.Value;
		}

		private void MefImport()
		{
			IEnumerable<ComposablePartCatalog> additionalComposablePartCatalogs = null;

			EventHandler<MEFImportEventArgs> handle = MefImporting;
			if (handle != null)
			{
				var mefImportEventArgs = new MEFImportEventArgs();
				handle(this, mefImportEventArgs);

				if (mefImportEventArgs.Cancel)
				{
					return;
				}

				additionalComposablePartCatalogs = mefImportEventArgs.AdditionalComposablePartCatalogs;
			}

			mefProvider = new MefProvider(
				(message, category) => System.Diagnostics.Trace.WriteLine(message, category), additionalComposablePartCatalogs);
			mefProvider.SatisfyImportsOnce(this);
		}

		#endregion

		/// <summary>
		/// Event args to influence the activation of MEF imports in the system.
		/// </summary>
		public class MEFImportEventArgs : EventArgs
		{
			#region Public Properties

			/// <summary>
			/// Gets or sets additional cataloges to inspect.
			/// </summary>
			public IEnumerable<ComposablePartCatalog> AdditionalComposablePartCatalogs { get; set; }

			/// <summary>
			/// Gets or sets a value indicating whether or not to use MEF import
			/// </summary>
			public bool Cancel { get; set; }

			#endregion
		}
	}
}